package com.ccrfid.rmc6.lbservice3.manager;

import com.ccrfid.rmc6.entity.personnel.PersonnelView;
import com.ccrfid.rmc6.entity.poi.PoiLocationEntity;
import com.ccrfid.rmc6.lbservice3.pojo.evt.LocationPersonnelChange;
import com.ccrfid.rmc6.lbservice3.pojo.locate.PersonnelLocateInfo;
import com.ccrfid.rmc6.lbservice3.pojo.msg.MsgLocate;
import com.ccrfid.rmc6.service.poi.PoiLocationService;
import com.ccrfid.rmc6.service.poi.PoiService;
import com.ccrfid.rmc6.util.JsonUtil;
import com.ccrfid.rmc6.util.SleepUtil;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

@Component
public class LocationManager {
    private static final Logger logger = LoggerFactory.getLogger(LocationManager.class);
    private List<Location> locationList = new CopyOnWriteArrayList<>();

    @Autowired
    private EventBus eventBus;
    @Autowired
    private PoiService poiService;
    @Autowired
    private PoiLocationService poiLocationService;

    @PostConstruct
    private void init() {
        initLocationData();
        eventBus.register(this);
    }

    @Subscribe
    public void onPersonnelLocateInfo(PersonnelLocateInfo locateInfo) {
        long startTime = System.currentTimeMillis();
        if (locateInfo.getPersonnelId() != null) {
            MsgLocate msgLocate = locateInfo.getMsgLocate();
            if (msgLocate.getCurrentPos() != null) {
                //1.通知离开区域: 区域内原来有此人，现在无此人
                locationList.parallelStream()
                        .filter(location -> location.containsPersonnel(locateInfo.getPersonnelId())
                                && (location.floorNotEquals(msgLocate.getCurrentFloorNumber())
                                || location.notContainsPos(msgLocate.getCurrentPos()))
                        ).forEach(location -> {
                            location.removePersonnel(locateInfo.getPersonnelId());
                            eventBus.post(new LocationPersonnelChange(location.getLocationId(), location.getPersonnelIdSet()));
                        });
                //2.通知进入区域: 区域内原来无此人，现在有此人
                locationList.parallelStream()
                        .filter(location -> location.notContainsPersonnel(locateInfo.getPersonnelId())
                                && location.floorEquals(msgLocate.getCurrentFloorNumber())
                                && location.containsPos(msgLocate.getCurrentPos())
                        ).forEach(location -> {
                            location.addPersonnel(locateInfo.getPersonnelId());
                            eventBus.post(new LocationPersonnelChange(location.getLocationId(), location.getPersonnelIdSet()));
                        });
            } else if (msgLocate.getCurrentPos() == null) {
                //1.通知离开区域：区域内原来有此人，现在此人位置为空
                locationList.parallelStream()
                        .filter(location -> location.containsPersonnel(locateInfo.getPersonnelId()))
                        .forEach(location -> {
                            location.removePersonnel(locateInfo.getPersonnelId());
                            eventBus.post(new LocationPersonnelChange(location.getLocationId(), location.getPersonnelIdSet()));
                        });
            }
        }
        long stopTime = System.currentTimeMillis();
        if (logger.isTraceEnabled()) {
            logger.trace("onPersonnelLocateInfo cost: {} ms, personnel: {}", stopTime - startTime, locateInfo.getPersonnelId());
        }
    }

    public LocationPersonnelChange getLocationPersonnelInfo(Long locationId) {
        Set<Long> personnelIdSet =
                locationList.parallelStream().filter(location -> location.getLocationId().equals(locationId))
                        .findFirst().get().getPersonnelIdSet();
        return new LocationPersonnelChange(locationId, personnelIdSet);
    }

    private void initLocationData() {
        List<PoiLocationEntity> locationEntities = poiLocationService.getAllLocations();
        if (locationEntities != null && locationEntities.size() > 0) {
            for (PoiLocationEntity locationEntity : locationEntities) {
                Location location = new Location(locationEntity.getId(), locationEntity.getLocationName(),
                        locationEntity.getLocationPolygon(), locationEntity.getMapLevel(),
                        locationEntity.getFloorNumber());
                List<PersonnelView> personnelList = poiService.getPersonnelsInLocation(locationEntity);
                if (personnelList != null && personnelList.size() > 0) {
                    for (PersonnelView personnel : personnelList) {
                        location.addPersonnel(personnel.getId());
                    }
                }
                locationList.add(location);
            }
        }
    }

    private class Location {
        private Long locationId;
        private String locationName;
        private Polygon locationPolygon;
        private Integer mapLevel;
        private Integer floorNumber;

        private Set<Long> personnelIdSet;       //区域内所有人员

        public Location(Long locationId, String locationName, Polygon locationPolygon,
                        Integer mapLevel, Integer floorNumber) {
            this.locationId = locationId;
            this.locationName = locationName;
            this.locationPolygon = locationPolygon;
            this.mapLevel = mapLevel;
            this.floorNumber = floorNumber;
            this.personnelIdSet = ConcurrentHashMap.newKeySet();
        }

        public void addPersonnel(Long personnelId) {
            personnelIdSet.add(personnelId);
        }

        public void removePersonnel(Long personnelId) {
            personnelIdSet.remove(personnelId);
        }

        public void clearPersonnel() {
            personnelIdSet.clear();
        }

        public boolean containsPersonnel(Long personnelId) {
            return personnelIdSet.contains(personnelId);
        }

        public boolean notContainsPersonnel(Long personnelId) {
            return !containsPersonnel(personnelId);
        }

        public boolean containsPos(Point pos) {
            return locationPolygon.contains(pos);
        }

        public boolean notContainsPos(Point pos) {
            return !containsPos(pos);
        }

        public boolean floorEquals(Integer floorNumber) {
            return this.floorNumber.equals(floorNumber);
        }

        public boolean floorNotEquals(Integer floorNumber) {
            return !floorEquals(floorNumber);
        }

        public Long getLocationId() {
            return locationId;
        }

        public void setLocationId(Long locationId) {
            this.locationId = locationId;
        }

        public String getLocationName() {
            return locationName;
        }

        public void setLocationName(String locationName) {
            this.locationName = locationName;
        }

        public Polygon getLocationPolygon() {
            return locationPolygon;
        }

        public void setLocationPolygon(Polygon locationPolygon) {
            this.locationPolygon = locationPolygon;
        }

        public Integer getMapLevel() {
            return mapLevel;
        }

        public void setMapLevel(Integer mapLevel) {
            this.mapLevel = mapLevel;
        }

        public Integer getFloorNumber() {
            return floorNumber;
        }

        public void setFloorNumber(Integer floorNumber) {
            this.floorNumber = floorNumber;
        }

        public Set<Long> getPersonnelIdSet() {
            return personnelIdSet;
        }

        public void setPersonnelIdSet(Set<Long> personnelIdSet) {
            this.personnelIdSet = personnelIdSet;
        }
    }

    //新增区域
    @JmsListener(destination = "ws_location_add", containerFactory = "topicListenerCF")
    public void onLocationAdd(String content) {
        SleepUtil.sleepSeconds(1);
        Long[] idList = JsonUtil.INSTANCE.readValue(content, Long[].class);
        for (Long locationId : idList) {
            addLocation(locationId);
        }
    }

    //更新区域
    @JmsListener(destination = "ws_location_update", containerFactory = "topicListenerCF")
    public void onLocationUpdate(String content) {
        SleepUtil.sleepSeconds(1);
        Long[] idList = JsonUtil.INSTANCE.readValue(content, Long[].class);
        for (Long locationId : idList) {
            updateLocation(locationId);
        }
    }

    //删除区域
    @JmsListener(destination = "ws_location_delete", containerFactory = "topicListenerCF")
    public void onLocationDelete(String content) {
        Long[] idList = JsonUtil.INSTANCE.readValue(content, Long[].class);
        for (Long locationId : idList) {
            deleteLocation(locationId);
        }
    }

    private void addLocation(Long locationId) {
        PoiLocationEntity locationEntity = poiLocationService.getPoiLocationById(locationId);
        if (locationEntity != null) {
            Location location = new Location(locationEntity.getId(), locationEntity.getLocationName(),
                    locationEntity.getLocationPolygon(), locationEntity.getMapLevel(),
                    locationEntity.getFloorNumber());
            List<PersonnelView> personnelList = poiService.getPersonnelsInLocation(locationEntity);
            if (personnelList != null && personnelList.size() > 0) {
                for (PersonnelView personnel : personnelList) {
                    location.addPersonnel(personnel.getId());
                }
            }
            locationList.add(location);
            //post msg
            eventBus.post(new LocationPersonnelChange(location.getLocationId(), location.getPersonnelIdSet()));
        }
    }

    private void updateLocation(Long locationId) {
        PoiLocationEntity locationEntity = poiLocationService.getPoiLocationById(locationId);
        if (locationEntity != null) {
            Location location = getLocation(locationId);
            if (location != null) {
                //更新状态
                location.setLocationName(locationEntity.getLocationName());
                location.setLocationPolygon(locationEntity.getLocationPolygon());
                location.setMapLevel(locationEntity.getMapLevel());
                location.setFloorNumber(locationEntity.getFloorNumber());
                //清空原有人员
                location.clearPersonnel();
                //添加新人员
                List<PersonnelView> personnelList = poiService.getPersonnelsInLocation(locationEntity);
                if (personnelList != null && personnelList.size() > 0) {
                    for (PersonnelView personnel : personnelList) {
                        location.addPersonnel(personnel.getId());
                    }
                }
                //post msg
                eventBus.post(new LocationPersonnelChange(location.getLocationId(), location.getPersonnelIdSet()));
            }
        }
    }

    private Location getLocation(Long locationId) {
        try {
            return locationList.parallelStream()
                    .filter(location -> location.getLocationId().equals(locationId))
                    .findFirst()
                    .get();
        } catch (NoSuchElementException ex) {
            return null;
        }
    }

    private void deleteLocation(Long locationId) {
        locationList.removeIf(location -> location.getLocationId().equals(locationId));
    }
}
